--- /dev/null
+import Gio from 'gi://Gio';
+import GLib from 'gi://GLib';
+
+import 'resource:///org/gnome/shell/ui/environment.js';
+import * as ExtensionUtils from 'resource:///org/gnome/shell/misc/extensionUtils.js';
+
+const fixturesDir = Gio.File.new_for_uri(`${import.meta.url}/../fixtures/extensions`);
+
+describe('loadExtensionMetadata()', () => {
+ const {loadExtensionMetadata} = ExtensionUtils;
+
+ it('fails if directory name does not match requested UUID', () => {
+ const dir = fixturesDir.get_child('valid');
+ expect(() => loadExtensionMetadata('invalid', dir))
+ .toThrowError(/does not match UUID/);
+ });
+
+ it('fails if metadata.json is missing', () => {
+ const dir = fixturesDir.get_child('empty');
+ expect(() => loadExtensionMetadata('empty', dir))
+ .toThrowError(/Missing metadata/);
+ });
+
+ it('fails if metadata.json is not valid JSON', () => {
+ const dir = fixturesDir.get_child('invalid');
+ expect(() => loadExtensionMetadata('invalid', dir))
+ .toThrowError(/parse metadata/);
+ });
+
+ it('fails if metadata.json misses "uuid" property', () => {
+ const dir = fixturesDir.get_child('missing-uuid');
+ expect(() => loadExtensionMetadata('missing-uuid', dir))
+ .toThrowError(/missing "uuid"/);
+ });
+
+ it('fails if metadata.json misses "name" property', () => {
+ const dir = fixturesDir.get_child('missing-name');
+ expect(() => loadExtensionMetadata('missing-name', dir))
+ .toThrowError(/missing "name"/);
+ });
+
+ it('fails if metadata.json misses "description" property', () => {
+ const dir = fixturesDir.get_child('missing-description');
+ expect(() => loadExtensionMetadata('missing-description', dir))
+ .toThrowError(/missing "description"/);
+ });
+
+ it('fails if metadata.json misses "shell-version" property', () => {
+ const dir = fixturesDir.get_child('missing-shell-version');
+ expect(() => loadExtensionMetadata('missing-shell-version', dir))
+ .toThrowError(/missing "shell-version"/);
+ });
+
+ it('fails if metadata.json "uuid" property is not a string', () => {
+ const dir = fixturesDir.get_child('invalid-uuid');
+ expect(() => loadExtensionMetadata('invalid-uuid', dir))
+ .toThrowError(/"uuid" is not of type/);
+ });
+
+ it('fails if metadata.json "shell-version" property is not an array', () => {
+ const dir = fixturesDir.get_child('invalid-shell-version1');
+ expect(() => loadExtensionMetadata('invalid-shell-version1', dir))
+ .toThrowError(/"shell-version" is not of type/);
+ });
+
+ it('fails if metadata.json "shell-version" property does not contain strings', () => {
+ const dir = fixturesDir.get_child('invalid-shell-version2');
+ expect(() => loadExtensionMetadata('invalid-shell-version2', dir))
+ .toThrowError(/"shell-version" is not of type/);
+ });
+
+ it('fails if metadata.json "uuid" property does not match directory name', () => {
+ const dir = fixturesDir.get_child('wrong-uuid');
+ expect(() => loadExtensionMetadata('wrong-uuid', dir))
+ .toThrowError(/does not match directory/);
+ });
+
+ it('loads valid metadata.json', () => {
+ const dir = fixturesDir.get_child('valid');
+ expect(() => loadExtensionMetadata('valid', dir)).not.toThrow();
+ });
+});
+
+describe('serializeExtension()', () => {
+ const {
+ loadExtensionMetadata, serializeExtension, deserializeExtension, ExtensionType,
+ } = ExtensionUtils;
+
+ // based on ExtensionManager
+ function createExtensionObject(uuid, dir, type) {
+ const metadata = loadExtensionMetadata(uuid, dir);
+ const extension = {
+ metadata,
+ uuid,
+ type,
+ dir,
+ path: dir.get_path(),
+ error: '',
+ hasPrefs: dir.get_child('prefs.js').query_exists(null),
+ enabled: false,
+ hasUpdate: false,
+ canChange: false,
+ sessionModes: metadata['session-modes'] ?? ['user'],
+ };
+ return extension;
+ }
+ const uuid = 'valid';
+ const ext = createExtensionObject(uuid,
+ fixturesDir.get_child(uuid), ExtensionType.PER_USER);
+ let serialized;
+
+ beforeAll(() => {
+ jasmine.addCustomEqualityTester((file1, file2) => {
+ if (file1 instanceof Gio.File && file2 instanceof Gio.File)
+ return file1.equal(file2);
+ return undefined;
+ });
+ });
+
+ it('produces output that can be used as variant of the expected type', () => {
+ expect(() => {
+ serialized = serializeExtension(ext);
+ }).not.toThrow();
+
+ let v;
+ expect(() => {
+ v = new GLib.Variant('a{sv}', serialized);
+ }).not.toThrow();
+
+ expect(v.is_of_type(new GLib.VariantType('a{sv}'))).toBeTrue();
+ });
+
+ it('produces output that can be deserialized', () => {
+ let deserialized;
+ expect(() => {
+ deserialized = deserializeExtension(serialized);
+ }).not.toThrow();
+
+ expect(deserialized).toEqual(ext);
+ });
+});